//
//  PLInteractor.m
//  Plankton
//
//  Created by Marc Schlichte on 23.10.08.
//  Copyright 2008-2009 Framework Labs. All rights reserved.
//

#import "PLInteractor.h"

#import "PLPlatform.h"

@interface PLInteractor (SubclassMethods)

@property(nonatomic, readonly) PLPlatform *platform;
@property(nonatomic, readonly) NSString *aid;

@end


@implementation PLInteractor

- (id)initWithContext:(id<PLInteractorContext>)context_
{
    if (self = [super init]) {
        context = context_;
    }
    return self;
}

- (PLPlatform *)platform
{
    return [context platformForInteractor:self];
}

- (NSString *)aid
{
    return [context aidForInteractor:self];
}

@end


@implementation PLResponse

- (id)initWithPlatform:(PLPlatform *)plat name:(NSString *)name_ sender:(NSString *)sender_ iid:(NSNumber *)iid_ receiver:(NSString *)receiver_
{
    if (self = [super init]) {
        platform = [plat retain];
        name = [name_ copy];
        sender = [sender_ copy];
        iid = [iid_ copy];
        receiver = [receiver_ copy];
    }
    return self;
}

- (void)dealloc
{
    [platform release];
    [name release];
    [sender release];
    [iid release];
    [receiver release];
    [super dealloc];
}

- (void)sendWithArgs:(id)args
{
    [platform send:[NSArray arrayWithObjects:@"response", name, sender, iid, args, nil]
                     to:receiver];
    [self release];
}

@end


@implementation PLResponder

- (void)dealloc
{
    [self.platform cancelReceive:pattern];
    [pattern release];
    [super dealloc];
}

- (void)serveRequestsNamed:(NSString *)reqName syncHandler:(SEL)reqHandler
{
    [self stopServingRequests];
    
    handler = reqHandler;
    
    pattern = [[PLPattern patternWithPredicateFormat:@"SELF[0] = 'request' AND SELF[1] = %@", reqName] retain];
    [pattern setMessageHandler:self];
    [self.platform receive:pattern by:self.aid];
}

- (void)serveRequestsNamed:(NSString *)reqName asyncHandler:(SEL)reqHandler
{
    isAsync = YES;
    [self serveRequestsNamed:reqName syncHandler:reqHandler];
}

- (void)stopServingRequests
{
    [self.platform cancelReceive:pattern];
    [pattern release], pattern = nil;
}

- (void)handleMessage:(id)message forPattern:(PLPattern *)pattern_
{
    NSString *name = [message objectAtIndex:1];
    NSString *sender = [message objectAtIndex:2];
    NSNumber *iid = [message objectAtIndex:3];
    id args = [message count] > 4 ? [message objectAtIndex:4] : nil;
    
    if (isAsync) {
        PLResponse *response = [[PLResponse alloc] initWithPlatform:self.platform name:name sender:self.aid iid:iid receiver:sender];
        [context performSelector:handler withObject:args withObject:response];
		[response release];
    }
    else {
        id replyArgs = [context performSelector:handler withObject:args];
        
        [self.platform send:[NSArray arrayWithObjects:@"response", name, self.aid, iid, replyArgs, nil]
						 to:sender];       
    }
    if (pattern) {
        [self.platform receive:pattern by:self.aid];
    }
}

@end


@implementation PLRequestor : PLInteractor

- (void)dealloc
{
    [iid release];
    [self.platform cancelReceive:responsePattern];
    [responsePattern release];
    [super dealloc];
}

- (void)setTimeoutHandler:(SEL)handler after:(NSTimeInterval)interval
{
    timeoutHandler = handler;
    timeoutInterval = interval;
}

- (void)sendRequestNamed:(NSString *)reqName arguments:(id)args 
                      to:(NSString *)respAid responseHandler:(SEL)handler
{
    if (responsePattern) {
        [NSException raise:NSInternalInconsistencyException format:@"a request is still pending"];
    }
    
    iid = [self.platform createTag];
    [self.platform send:[NSArray arrayWithObjects:@"request", reqName, self.aid, iid, args, nil]
                     to:respAid];
    
    responseHandler = handler;
    
    responsePattern = [[PLPattern patternWithPredicateFormat:@"SELF[0] = 'response' AND SELF[1] = %@ AND SELF[2] = %@ AND SELF[3] = %@", reqName, respAid, iid] retain];
    responsePattern.userInfo = respAid;
    
    [responsePattern setMessageHandler:self];
    if (timeoutHandler) {
        [responsePattern setTimeoutHandler:self after:timeoutInterval];
    }
    
    [self.platform receive:responsePattern by:self.aid];
}

- (void)handleMessage:(id)message forPattern:(PLPattern *)pattern_
{
    [responsePattern release], responsePattern = nil;
    if (responseHandler) {  // TODO: better assert this 
        if (responseHandler == @selector(release)) {
            [self release];
        }
        else {
			id args = [message count] > 4 ? [message objectAtIndex:4] : nil;
            [context performSelector:responseHandler withObject:args withObject:self];
        }
    }
}

- (void)handleTimeoutForPattern:(PLPattern *)pattern
{
    [context performSelector:timeoutHandler withObject:pattern.userInfo withObject:self];
}

@end


@implementation PLMulticastRequestor

- (void)dealloc
{
    [outstandingPeers release];
    [super dealloc];
}

- (void)_checkIfDone
{
    if ([outstandingPeers count] == 0) {
        [outstandingPeers release], outstandingPeers = nil;
        
        [context performSelector:joinHandler withObject:self];
    }
}

- (void)multicastRequestNamed:(NSString *)name arguments:(id)args 
                           to:(id<NSFastEnumeration>)peers 
              responseHandler:(SEL)responseHandler_
                  joinHandler:(SEL)joinHandler_
{
    if (outstandingPeers) {
        [NSException raise:NSInternalInconsistencyException format:@"a request is still pending"];
    }
    
    iid = [[self.platform createTag] retain];
    responseHandler = responseHandler_;
    joinHandler = joinHandler_;
    
    outstandingPeers = [[NSMutableSet alloc] init];
    for (id peer in peers) {
        [outstandingPeers addObject:peer];
    }
    
    if ([outstandingPeers count] == 0) {
        [self _checkIfDone];
        return;
    }    

    for (id peer in peers) {      
        [self.platform send:[NSArray arrayWithObjects:@"request", name, self.aid, iid, args, nil]
                         to:peer];
    
        PLPattern *pattern = [PLPattern patternWithPredicateFormat:@"SELF[0] = 'response' AND SELF[1] = %@ AND SELF[2] = %@ AND SELF[3] = %@", name, peer, iid];
        pattern.userInfo = peer;
        
        [pattern setMessageHandler:self];
        if (timeoutHandler) {
            [pattern setTimeoutHandler:self after:timeoutInterval];
        }
        
        [self.platform receive:pattern by:self.aid];
    }
}

- (void)handleMessage:(id)message forPattern:(PLPattern *)pattern
{
	id args = [message count] > 4 ? [message objectAtIndex:4] : nil;
    [context performSelector:responseHandler withObject:args withObject:self];
    
    [outstandingPeers removeObject:pattern.userInfo];
    [self _checkIfDone];
}

- (void)handleTimeoutForPattern:(PLPattern *)pattern
{
    NSString *peer = pattern.userInfo;
    [context performSelector:timeoutHandler withObject:peer withObject:self];
    
    [outstandingPeers removeObject:peer];
    [self _checkIfDone];
}    

@end

